/*****************************************************************************
*  Copyright Statement:
*  --------------------
*  This software is protected by Copyright and the information contained
*  herein is confidential. The software may not be copied and the information
*  contained herein may not be used or disclosed except with the written
*  permission of MediaTek Inc. (C) 2005
*
*  BY OPENING THIS FILE, BUYER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
*  THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
*  RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO BUYER ON
*  AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
*  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
*  NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
*  SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
*  SUPPLIED WITH THE MEDIATEK SOFTWARE, AND BUYER AGREES TO LOOK ONLY TO SUCH
*  THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. MEDIATEK SHALL ALSO
*  NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO BUYER'S
*  SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN FORUM.
*
*  BUYER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND CUMULATIVE
*  LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
*  AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
*  OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY BUYER TO
*  MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
*
*  THE TRANSACTION CONTEMPLATED HEREUNDER SHALL BE CONSTRUED IN ACCORDANCE
*  WITH THE LAWS OF THE STATE OF CALIFORNIA, USA, EXCLUDING ITS CONFLICT OF
*  LAWS PRINCIPLES.  ANY DISPUTES, CONTROVERSIES OR CLAIMS ARISING THEREOF AND
*  RELATED THERETO SHALL BE SETTLED BY ARBITRATION IN SAN FRANCISCO, CA, UNDER
*  THE RULES OF THE INTERNATIONAL CHAMBER OF COMMERCE (ICC).
*
*****************************************************************************/

/*******************************************************************************
 *
 * Filename:
 * ---------
 * amr_drv.c
 *
 * Project:
 * --------
 *   MAUI
 *
 * Description:
 * ------------
 *   AMR/AMR-WB codec driver
 *
 * Author:
 * -------
 * -------
 *
 *==============================================================================
 *             HISTORY
 * Below this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
 *------------------------------------------------------------------------------
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 * removed!
 * removed!
 * removed!
 *
 *------------------------------------------------------------------------------
 * Upper this line, this part is controlled by PVCS VM. DO NOT MODIFY!!
 *==============================================================================
 *******************************************************************************/
#include "media.h"
#include "fsal.h"
#include "l1sp_trc.h"
#include "amr_drv.h"

#if defined(AMR_CODEC)

#include "amr_table.h"
#include "am.h"
#include "afe.h"


// AWB state machine
#define DSP_IDLE              0
#define DSP_INITIALIZED       1
#define DSP_PLAYING           2
#define DSP_STOP              3


#define AMR_STATE_PLAY        1
#define AMR_STATE_PAUSE       2
#define AMR_STATE_IDLE        3

#define FRAMES_PER_SEC        50

kal_int32 amrGetBytesFromFile(MHdl *hdl, STFSAL *pstFSAL, kal_uint32 fileOffset, kal_uint32 num_bytes, kal_uint8 *ptr)
{
   kal_uint32 file_size, data_size;

   FSAL_GetFileSize(pstFSAL, &file_size);
   data_size = file_size - fileOffset;
   if(data_size <= num_bytes)
      num_bytes = data_size;

   FSAL_Seek( pstFSAL, fileOffset);
   if(FSAL_Read_Bytes( pstFSAL, ptr, num_bytes) != FSAL_OK)
      return -1;

   return num_bytes;
}

static void media_playback_error(amrMediaHdl *ihdl)
{
   ihdl->end_status = MEDIA_ERROR;
   mhdlException( (MHdl*)ihdl, MEDIA_ERROR );
}

static kal_uint8 media_buffer_get_byte(amrMediaHdl *ihdl)
{
   if (ihdl->mh.rbInfo.read != ihdl->mh.rbInfo.write) {
   	kal_uint8 wVal = ihdl->mh.rbInfo.rb_base[ihdl->mh.rbInfo.read++];
      if (ihdl->mh.rbInfo.read >= ihdl->mh.rbInfo.rb_size)
         ihdl->mh.rbInfo.read = 0;
      return wVal;
   }
   ASSERT(ihdl->mh.rbInfo.read != ihdl->mh.rbInfo.write);   /* media ring buffer underflow */
   return 0;
}

static kal_uint8 media_buffer_peek_byte(amrMediaHdl *ihdl)
{
   if (ihdl->mh.rbInfo.read != ihdl->mh.rbInfo.write) {
      return ihdl->mh.rbInfo.rb_base[ihdl->mh.rbInfo.read];
   }
   ASSERT( ihdl->mh.rbInfo.read != ihdl->mh.rbInfo.write );   /* media ring buffer underflow */
   return 0;
}

static void media_buffer_get_nbytes(amrMediaHdl *ihdl, kal_uint8 *buf, kal_uint32 uLen)
{
   kal_uint32 i=0;
   ASSERT(buf!=NULL);
   while(uLen-->0) {
      buf[i++] = media_buffer_get_byte(ihdl);
   }
}

static void amr_write_speech_mode_to_dsp( amrMediaHdl *ihdl, kal_uint8 bTxType)
{
#if defined(AMRWB_DECODE)
   if (KAL_TRUE == ihdl->amr.bWideBand) {
      *DP_SD2_AWB_VM_0 = (ihdl->amr.bCodecMode << 8) | bTxType; /* codec mode | Tx-Type */
   } else
#endif
   {
      *DP_SD2_VM_17 = ihdl->amr.bCodecMode;
      *DP_SD2_VM_0 = bTxType;
   }
}

static void amr_write_speech_data_to_dsp(amrMediaHdl *ihdl, kal_uint16 *pwData, kal_uint16 bLen)
{
   volatile kal_uint16 *pwSherifAddr;
   kal_uint8 i;
#if defined(AMRWB_DECODE)
   if (KAL_TRUE==ihdl->amr.bWideBand) {
      pwSherifAddr = DP_SD2_AWB_VM_1;
      if (bLen>17) {
         for(i=0; i<17; i++) {
   	    *pwSherifAddr++ = *pwData++;
   	 }
   	 pwSherifAddr = DP_SD2_AWB_VM_18;
   	 for(; i< bLen; i++) {
   	    *pwSherifAddr++ = *pwData++;
   	 }
      } else {
         for(i=0; i < bLen; i++ )
         *pwSherifAddr++ = *pwData++;
      }
   } else
#endif
   {
      pwSherifAddr = DP_SD2_VM_1;
      for (i = 0; i < bLen; i++)
         *pwSherifAddr++ = *pwData++;
   }
}

static void amr_bit_ordering_file_to_sherif(amrMediaHdl *ihdl, kal_uint8 bTxType, kal_uint8 bFrameType, kal_uint16* pInData, kal_uint16* pOutData, kal_uint16 wLenBits)
{
   kal_uint16 i, k, p, q;

   k = 0;
#if defined(AMRWB_DECODE)
   if (KAL_TRUE == ihdl->amr.bWideBand) {
      const kal_uint16* pOrderingTable = tblAMR_WBOrder[bFrameType];
      for(i=0, p=0, q=0; i<wLenBits; i++){
         if (bTxType==2) { /* Speech */
      	    k = (kal_uint16)pOrderingTable[i];
         } else if (bTxType==3) { /* SID first or SID update */
            k=i;
         } else {ASSERT(KAL_FALSE);}
         pOutData[k/16] |= (pInData[q] & AMR_ord_tab[p]) ? AMR_get_tab[k%16] : 0;
         if(++p==16) { p=0; q++; }
      }
   } else
#endif
   {
      const kal_uint8* pOrderingTable = tblAMROrder[bFrameType];
      for(i=0, p=0, q=0; i<wLenBits; i++){
         if (bTxType==2) { /* Speech */
      	    k = (kal_uint16)pOrderingTable[i];
         } else if (bTxType==3) { /* SID first or SID update */
            k=i;
         } else {ASSERT(KAL_FALSE);}
         pOutData[k/16] |= (pInData[q] & AMR_ord_tab[p]) ? AMR_get_tab[k%16] : 0;
         if(++p==16) { p=0; q++; }
      }
   }
}

kal_uint32 amrChkFrameHeader( amrMediaHdl *ihdl, kal_uint8  bFrameHeader, kal_uint8 *Tx_Type, kal_uint16 *lenSherifData, kal_uint16 *rd_bits )
{
   kal_uint8  FT, FQI;
   FT = (bFrameHeader >> 3) & 0x0F;
   FQI = (bFrameHeader >> 2) & 0x01;

   if (FT<=7) { /* AMR 4.75 ~ 12.2 kbps */
      ihdl->amr.bCodecMode = FTtoSCM_AMR[FT];
      *rd_bits = lenSpeechBitsAMR[FT];
      *Tx_Type = 2;
      *lenSherifData = (*rd_bits+15)/16;
   } else if (FT==8) { /* AMR SID */
      *rd_bits = lenSpeechBitsAMR[FT];
      *Tx_Type = 3;
      *lenSherifData = 3;
   } else if (FT>=9 && FT<=14) { /* GSM/TDMA/PDC SID and 'for-future-use' mode */
      return HEADER_ERROR;
   } else if (FT==15) { /* No_Data*/
   	*rd_bits = 0;
   	*lenSherifData = 0;
      *Tx_Type = 0;
      return HEADER_NO_DATA_FRAME;
   }
   return HEADER_OK;
}

#if defined(AMRWB_DECODE)
kal_int32 amrWBChkFrameHeader( amrMediaHdl *ihdl, kal_uint8  bFrameHeader, kal_uint8 *Tx_Type, kal_uint16 *lenSherifData, kal_uint16 *rd_bits )
{
   kal_uint8  FT, FQI;
   FT = (bFrameHeader >> 3) & 0x0F;
   FQI = (bFrameHeader >> 2) & 0x01;

   if (FT<=8) { /* AMR-WB 6.60 kbps ~ 23.85 kbps */
      *Tx_Type = 2;
      ihdl->amr.bCodecMode = FTtoSCM_AMR_WB[FT];
      *rd_bits = lenSpeechBitsAMR_WB[FT];
      *lenSherifData = (*rd_bits+15)/16;
   } else if (FT==9) { /* AMR-WB SID */
      *Tx_Type = 3;
      *rd_bits = lenSpeechBitsAMR_WB[FT];
      *lenSherifData = 3;
   } else if (FT==14) { /* SPEECH_LOST */
      if (FQI==0) {
         *rd_bits = 0;
         *lenSherifData = 0;
         *Tx_Type = 0;
      } else {
         return HEADER_ERROR; /* undefined situation */
      }
   } else if (FT==15) { /* NO_DATA */
      *rd_bits = 0;
      *lenSherifData = 0;
      *Tx_Type = 0;
      return HEADER_NO_DATA_FRAME;
   } else { /* 'for-future-use' mode */
   	 return HEADER_ERROR;
   }
   return HEADER_OK;
}

#endif


static void amrHisr( void *data )
{
   kal_int32  uBytesAvailable;
   kal_uint16 rd_bytes, lenSherifData, rd_bits;
   kal_uint8  bFrameHeader, FT, Tx_Type;

   amrMediaHdl *ihdl = (amrMediaHdl *)data;

   if (ihdl->mh.state != AMR_STATE_PLAY || ihdl->end_status == MEDIA_END
       || ihdl->end_status == MEDIA_ERROR || ihdl->end_status == MEDIA_TERMINATED)
      return;

#if VERIFY_AVSYNC
   ut_avsync_play_hisr();
#endif

   /* check ring buffer emptiness */
   uBytesAvailable = ihdl->mh.GetDataCount((MHdl *)ihdl);
   uBytesAvailable --;  /* 1 byte for header */

   if (uBytesAvailable < 1) {
      /* media buffer underflows */
      /* stuff 'no-data' frame */
      amr_write_speech_mode_to_dsp( ihdl, 0 );
      if (ihdl->mh.eof) {
         ihdl->end_status = MEDIA_END;
         mhdlException( (MHdl*)ihdl, MEDIA_END );
      }
      else
         mhdlDataRequest((MHdl*)ihdl, ihdl->end_status);
      return;
   }

   /* decode frame header */
   bFrameHeader = media_buffer_peek_byte( ihdl );
   FT = (bFrameHeader >> 3) & 0x0F;

#if defined(__STREAM_REC_SUPPORT__)
   ihdl->mh.recRead = ihdl->mh.rbInfo.read;
#endif

#if defined(AMRWB_DECODE)
   if (KAL_TRUE==ihdl->amr.bWideBand) {
      /* If the codec mode are not right, DSP may mal-function */
      EXT_ASSERT((((((*DP_SD2_AWB_VM_0)>>8)&0xFF)>=0x20) &&
        ((((*DP_SD2_AWB_VM_0)>>8)&0xFF)<=0x28)), (*DP_SD2_AWB_VM_0), 0, 0);

      {
         kal_int32 ret;
         ret = amrWBChkFrameHeader( ihdl, bFrameHeader, &Tx_Type, &lenSherifData, &rd_bits );
         if(ret == HEADER_ERROR){
            media_playback_error(ihdl);
            return;
         }else if(ret == HEADER_NO_DATA_FRAME){
             media_buffer_get_byte(ihdl);   /* consume the header byte */
             ihdl->mh.current_time += 20;
             ihdl->lastFrameOffset += 1;
             amr_write_speech_mode_to_dsp(ihdl, Tx_Type);

#if defined(__STREAM_REC_SUPPORT__)
             if (ihdl->isStreaming) {
                // Record the RTP data
                ihdl->mh.timeStamp += ihdl->mh.timeStampInterval;
                audRTP_putRecDataCircular(&ihdl->mh, 1);
             }
#endif

             return;
          }
      }

   } else
#endif
   {
      /* If the codec mode are not right, DSP may mal-function */
      EXT_ASSERT(((*DP_SD2_VM_17>=0x03) && (*DP_SD2_VM_17<=0x0A)), (*DP_SD2_VM_17), 0, 0);
      {
         kal_uint32 ret;
         ret = amrChkFrameHeader( ihdl, bFrameHeader, &Tx_Type, &lenSherifData, &rd_bits );
         if(ret == HEADER_ERROR){
            media_playback_error(ihdl);
            return;
         }else if(ret == HEADER_NO_DATA_FRAME){
             media_buffer_get_byte(ihdl);   /* consume the header byte */
             ihdl->mh.current_time += 20;
             ihdl->lastFrameOffset += 1;
             amr_write_speech_mode_to_dsp(ihdl, Tx_Type);

#if defined(__STREAM_REC_SUPPORT__)
             if (ihdl->isStreaming) {
                // Record the RTP data
                ihdl->mh.timeStamp += ihdl->mh.timeStampInterval;
                audRTP_putRecDataCircular(&ihdl->mh, 1);
             }
#endif

             return;
          }
      }
   }

   /* check buffer underflow */
   rd_bytes = (rd_bits+7)>>3;
   if (rd_bytes > uBytesAvailable) {
      amr_write_speech_mode_to_dsp( ihdl, 0 );
      if (ihdl->mh.eof) {
         ihdl->end_status = MEDIA_END;
         mhdlException( (MHdl*)ihdl, MEDIA_END );
         return;
      }
      mhdlDataRequest((MHdl*)ihdl, ihdl->end_status);
      return;
   }

   if(ihdl->mh.current_time >= ihdl->mh.stop_time) {
      ihdl->end_status = MEDIA_END;
      mhdlException( (MHdl*)ihdl, MEDIA_STOP_TIME_UP );
      return;
   }

   ihdl->mh.current_time += 20;
   ihdl->lastFrameOffset += (rd_bytes+1);
   if(ihdl->AudioSeekTbl != 0)
   {  
      AST_UpdateTable(ihdl->AudioSeekTbl, ihdl->lastFrameOffset, ihdl->mh.current_time / FRAME_DURATION);
      if (!ihdl->isStreaming)
      {
         if ( ((ihdl->mh.current_time / FRAME_DURATION) & 0x1F) == 0x1F )
         {
            mhdlDataRequest((MHdl*)ihdl, MEDIA_UPDATE_DUR);
         }
      }
   }

   media_buffer_get_byte(ihdl);   /* consume the header byte */
   /* get speech data from ring buffer */
   media_buffer_get_nbytes( ihdl, (kal_uint8*)ihdl->Tx_ordering_buffer, rd_bytes);

#if defined(__STREAM_REC_SUPPORT__)
   // Record the RTP data
   if (ihdl->isStreaming) {
      ihdl->mh.timeStamp += ihdl->mh.timeStampInterval;
      audRTP_putRecDataCircular(&ihdl->mh, 1+rd_bytes);
   }
#endif

   /* bits re-ordering */
   kal_mem_set(ihdl->Tx_data_buffer, 0, sizeof(ihdl->Tx_data_buffer));
   amr_bit_ordering_file_to_sherif(ihdl, Tx_Type, FT, ihdl->Tx_ordering_buffer, ihdl->Tx_data_buffer, rd_bits);

   if (Tx_Type==3) { /* SID first or SID update */
      ihdl->Tx_data_buffer[2]&=0x00E0;
      if((ihdl->Tx_ordering_buffer[2]&0x0010)==0)   /* STI */
         Tx_Type = 1; /* SID first */
   }

   /* write speech frame to sherif */
   amr_write_speech_mode_to_dsp(ihdl, Tx_Type);
   amr_write_speech_data_to_dsp(ihdl, ihdl->Tx_data_buffer, lenSherifData);

   mhdlDataRequest((MHdl*)ihdl, ihdl->end_status);
}

static kal_uint16 amr_SetAndGetInitialCodecMode( amrMediaHdl *ihdl )
{
   kal_uint16 wInitialCodecMode;

#if defined(AMRWB_DECODE)
   if (KAL_TRUE == ihdl->amr.bWideBand) {
      ihdl->amr.bCodecMode = 0x20;
      wInitialCodecMode = (0x20<<2);
   } else
#endif
   {
      ihdl->amr.bCodecMode = 0x03;
      wInitialCodecMode = (0x03<<2) | (0x03<<6);
   }
   return wInitialCodecMode;
}

#if defined(MT6225) || defined(MT6229) || defined(MT6268T) || defined(MT6235) || defined(MT6238) || defined(MT6235B) || defined(MT6239) || defined(MT6268A) || defined(MT6268) || defined(MT6253T) || defined(MT6253)
#if defined(AMRWB_DECODE) || defined(AMRWB_ENCODE)
static void amr_WBStopCtrl( amrMediaHdl *ihdl, bool isPlayback )
{
   kal_uint16  I, ctl;
   volatile kal_uint16* ptr;

   ptr = ((isPlayback == true )?DP_AWB_SD_CTRL:DP_AWB_SE_CTRL);

   if(*ptr == DSP_PLAYING){
      ihdl->mh.state = AMR_STATE_IDLE;
      *ptr = DSP_STOP;
      kal_trace( TRACE_STATE, L1AUDIO_STOP_STATE );
   }
   for( I = 0; ; I++ ) {
      ctl = *ptr;
      if( ctl == DSP_IDLE ){                /* DSP returns to idle state */
         kal_trace( TRACE_STATE, L1AUDIO_IDLE_STATE );
         break;
      }
      ASSERT_REBOOT( I < 20 );
      kal_sleep_task( 2 );
   }
}
#endif
#endif

void amrOpenDeviceCommon( kal_uint16 wCodecMode, kal_uint32 playType, kal_uint8 volumeLvl)
{
#if AMR_AWB_AUDIO_PATH_SUPPORT
   {
      uint16 control = wCodecMode ;
      uint32 vm_control  = (playType<<16) | control;
      uint16 sc_mode = 0, sd_mode = 0;
      uint16 codec_mode = 0;
      uint16 amr_sd_mode = (control&0x3C) >> 2;
      uint16 awb_sd_mode = (control&0xFC) >> 2;
      uint16 samplingrate = 0;
      /* Set Digital Gain for AMR/AMR-WB */
      AFE_SetDigitalGain( L1SP_AUDIO, volumeLvl );

      if( ((0x03<=amr_sd_mode) && (0x0A>=amr_sd_mode))){ // AMR
         DSP_DynamicDownload( DDID_AMRSBC );
         codec_mode = ASP_TYPE_AMR;
         sc_mode = (vm_control>>2) & 0xF;
         sd_mode = (vm_control>>6) & 0xF;
         samplingrate = ASP_FS_8K;
      }
#if defined(AMRWB_DECODE)
      else if ((0x20<=awb_sd_mode) && (0x28>=awb_sd_mode)){  // AWB
          DSP_DynamicDownload(DDID_AWBDEC);
          codec_mode = ASP_TYPE_AWBDEC;
          sc_mode = (vm_control>>2) & 0xFF;
          sd_mode = 0;
          samplingrate = ASP_FS_16K;
      }
#endif
      if( sc_mode == 0 )
         sc_mode = sd_mode;
      if( sd_mode == 0 )
         sd_mode = sc_mode;

      *AMR_AWB_AUDIO_PATH_CTRL = 0x40;
      if( sc_mode > 2){
         *AMR_PARAM1 = 0x1622;
      }else{
          *AMR_PARAM1 = 0x2622;
      }
      *AMR_PARAM2 = sd_mode | (sc_mode << 8);
      *DP_AUDIO_FLEXI_CTRL = (FLEXI_VBI_ENABLE | FLEXI_SD_ENABLE);
      AM_AudioPlaybackOn( codec_mode, samplingrate);
   }
#else // AMR_AWB_AUDIO_PATH_SUPPORT
   /* Set Digital Gain for AMR/AMR-WB */
   AFE_SetDigitalGain( L1SP_VOICE, volumeLvl );
   AM_VMPlaybackOn( wCodecMode, playType);
#endif
}

static void amrOpenDevice( amrMediaHdl *ihdl , kal_uint16 wCodecMode)
{
   ihdl->end_status = MEDIA_NONE;
   mhdlDisallowSleep( (MHdl*)ihdl );

   KT_StopAndWait();
   TONE_StopAndWait();

   amrOpenDeviceCommon( wCodecMode, ihdl->amr.vm, ihdl->mh.volume_level);
   ihdl->mh.state = AMR_STATE_PLAY;

#if defined(AMRWB_DECODE) && ( defined(MT6225) || defined(MT6229) || defined(MT6268T) || defined(MT6235) || defined(MT6238) || defined(MT6235B) || defined(MT6239) || defined(MT6268A) || defined(MT6268) || defined(MT6253T) || defined(MT6253) )
   if (MEDIA_FORMAT_AMR_WB == ihdl->mh.mediaType ){
      kal_uint32 I;
      kal_uint16 ctl;
      volatile kal_uint16* ptr;

      *DP_AWB_SD_CTRL = DSP_INITIALIZED;
      ptr = DP_AWB_SD_CTRL;
      for( I = 0; ; I++ ) {
         ctl = *ptr;
         if( ctl == DSP_PLAYING ){                /* DSP returns to idle state */
            kal_trace( TRACE_STATE, L1AUDIO_RUNNING_STATE );
            break;
         }
         ASSERT_REBOOT( I < 20 );
         kal_sleep_task( 2 );
      }
   }
#endif

#if AMR_AWB_AUDIO_PATH_SUPPORT
   {
      kal_uint32 uCnt;
      for( uCnt = 0; ; uCnt++ ) {
         if( *AMR_AWB_AUDIO_PATH_CTRL == 0 ){
            break;
         }
         ASSERT_REBOOT( uCnt < 20 );
         kal_sleep_task( 2 );
      }
   }
#endif // AMR_AWB_AUDIO_PATH_SUPPORT

}

static void amrCloseDevice( amrMediaHdl *ihdl )
{
#if defined(AMRWB_DECODE) && ( defined(MT6225) || defined(MT6229) || defined(MT6268T) || defined(MT6235) || defined(MT6238) || defined(MT6235B) || defined(MT6239) || defined(MT6268A) || defined(MT6268) || defined(MT6253T) || defined(MT6253) )
   if(ihdl->amr.bWideBand)
      amr_WBStopCtrl( ihdl, true );
#endif

#if AMR_AWB_AUDIO_PATH_SUPPORT
   *DP_AUDIO_FLEXI_CTRL = 0;
   AM_AudioPlaybackOff( true );
#else // AMR_AWB_AUDIO_PATH_SUPPORT
   AM_VMPlaybackOff(ihdl->amr.vm);
#endif
   mhdlAllowSleep( (MHdl*)ihdl );
}

kal_bool amrChkFileHeader( amrMediaHdl *ihdl )
{
   kal_uint8 AMR_Header[9];
   /* check amr-wb file header */
   if(amrGetBytesFromFile((MHdl*)ihdl, ihdl->pstFSAL, ihdl->fileOffset, 9, AMR_Header) != 9)
      return KAL_FALSE;
#if defined(AMRWB_DECODE)
   if (memcmp(AMR_Header, "#!AMR-WB\n", 9)==0){
      ihdl->mh.mediaType = MEDIA_FORMAT_AMR_WB;
      ihdl->fileOffset += 9;
   }else
#endif
   {
      if(memcmp(AMR_Header, "#!AMR\n", 6)!=0)
         return KAL_FALSE;
      ihdl->mh.mediaType = MEDIA_FORMAT_AMR;
      ihdl->fileOffset += 6;
   }

   return KAL_TRUE;
}

static Media_Status amrMFClose( MHdl *hdl )
{
   ASSERT( hdl != 0 && hdl->state != AMR_STATE_PLAY);
   if( hdl->handler != NULL )
      mhdlFinalize( hdl, DP_D2C_SD_DONE );

#if VERIFY_AVSYNC
   ut_a2v_dsp_stop();
#endif

   free_ctrl_buffer( hdl );
   kal_trace( TRACE_FUNC, L1AUDIO_CLOSE, hdl->mediaType );
   return MEDIA_SUCCESS;
}


static kal_bool amrFileMediaBuffer(MHdl *hdl)
{
   amrMediaHdl *ihdl = (amrMediaHdl *)hdl;
   kal_uint8 *pBuf;
   kal_int32 num_bytes, cnt = 0;
   kal_uint32 buf_len;

   do {
      hdl->GetWriteBuffer(hdl, &pBuf, &buf_len);
      if (buf_len == 0 || hdl->eof ) break;
      num_bytes = autGetBytesFromFile(hdl, ihdl->pstFSAL, ihdl->fileOffset, buf_len, pBuf);
      if (num_bytes < 0)
         return false;
      ihdl->fileOffset += num_bytes;
      hdl->WriteDataDone( hdl, num_bytes );
   } while(++cnt <= 2);
   
   return true;
}

static Media_Status amrMFPlayFile( MHdl *hdl )
{
   kal_uint16 wCodecMode;
   amrMediaHdl *ihdl = (amrMediaHdl *)hdl;

   if(hdl->start_time || hdl->current_time){
      ihdl->fileOffset = 0;
      ihdl->end_status = MEDIA_NONE;
      hdl->rbInfo.read = 0;
      hdl->rbInfo.write = 0;
      hdl->eof = KAL_FALSE;
      hdl->waiting = KAL_FALSE;
      hdl->current_time = hdl->start_time;
   }

   if(amrChkFileHeader( ihdl ) == KAL_FALSE)
      return MEDIA_BAD_FORMAT;

   if(ihdl->AudioSeekTbl != 0)
   {
   	  AST_PortTable(ihdl->AudioSeekTbl,amrASTParseFrame,amrASTParseFileHeader);
      AST_UpdateTable(ihdl->AudioSeekTbl, ihdl->fileOffset, 0);
   }

#if defined(AMRWB_DECODE)
   if (MEDIA_FORMAT_AMR_WB == hdl->mediaType ) { /* AMR-WB */
      ihdl->amr.bWideBand = KAL_TRUE;
      ihdl->amr.vm |= (kal_uint32)MEDIA_VMP_AMR_WB;
   }else 
#endif
   if (MEDIA_FORMAT_AMR == hdl->mediaType ) { /* AMR */
      ihdl->amr.bWideBand = KAL_FALSE;
   }

   if(hdl->start_time){
      FSAL_GetFileSize(ihdl->pstFSAL, &ihdl->uFileSize);
      if ((amrSeek(ihdl, true)==KAL_FALSE) || (ihdl->fileOffset >= ihdl->uFileSize))
         return MEDIA_SEEK_EOF;
   }else{
      ihdl->lastFrameOffset = ihdl->fileOffset;
   }

   // Fill media buffer
   if ( false == amrFileMediaBuffer(hdl) )
      return MEDIA_READ_FAIL;

#if !AMR_AWB_AUDIO_PATH_SUPPORT
#if defined(AMRWB_DECODE) && ( defined(MT6229) )
   if(ihdl->amr.bWideBand == KAL_TRUE){
      DSP_DynamicDownload( DDID_AWB );
      kal_trace( TRACE_STATE, L1AUDIO_INIT_STATE );
   }
#endif
#endif

   wCodecMode = amr_SetAndGetInitialCodecMode( ihdl );

   /* write 'No-Data' speech frame to dsp */
   amr_write_speech_mode_to_dsp( ihdl, 0 );

   amrOpenDevice( ihdl, wCodecMode );

   kal_trace( TRACE_FUNC, L1AUDIO_PLAY_FILE, hdl->mediaType );

   return MEDIA_SUCCESS;
}

static Media_Status amrMFStop( MHdl *hdl )
{
   amrMediaHdl *ihdl = (amrMediaHdl *)hdl;

   if( hdl->state == AMR_STATE_PAUSE || hdl->state == AMR_STATE_IDLE )
      return MEDIA_SUCCESS;

#if APM_SUPPORT
   AFE_SetHardwareMute(true);
#endif
   ihdl->end_status = MEDIA_TERMINATED;

   amrCloseDevice( ihdl );
   hdl->uSeekProgress = 0;
   hdl->uCurSeekFrm = 0;
   hdl->state = AMR_STATE_IDLE;
   kal_trace( TRACE_FUNC, L1AUDIO_STOP, hdl->mediaType );
   return MEDIA_SUCCESS;
}

static Media_Status amrMFPause( MHdl *hdl )
{
   amrMediaHdl *ihdl = (amrMediaHdl *)hdl;

   ASSERT( hdl->state == AMR_STATE_PLAY );
#if APM_SUPPORT
   AFE_SetHardwareMute(true);
#endif
   ihdl->end_status = MEDIA_TERMINATED;

   amrCloseDevice( ihdl );
   hdl->uSeekProgress = 0;
   hdl->uCurSeekFrm = 0;
   hdl->state = AMR_STATE_PAUSE;

   kal_trace( TRACE_FUNC, L1AUDIO_PAUSE, hdl->mediaType );

   return MEDIA_SUCCESS;
}

static Media_Status amrMFResumeFile( MHdl *hdl )
{
   kal_uint16 wCodecMode;
   amrMediaHdl *ihdl = (amrMediaHdl *)hdl;

   ASSERT( hdl->state == AMR_STATE_PAUSE );
   hdl->waiting = KAL_FALSE;

   wCodecMode = amr_SetAndGetInitialCodecMode( ihdl );
         /* write 'No-Data' speech frame to dsp */
   amr_write_speech_mode_to_dsp( ihdl, 0 );

   amrOpenDevice( ihdl , wCodecMode );
   kal_trace( TRACE_FUNC, L1AUDIO_RESUME, hdl->mediaType );
   return MEDIA_SUCCESS;
}

static Media_Event amrMFStrmProcess( MHdl *hdl, Media_Event event )
{
   amrMediaHdl *ihdl = (amrMediaHdl *) hdl;
   ASSERT(ihdl != NULL);

   kal_trace( TRACE_GROUP_AUD_PROCESS, L1AUDIO_PROCESS, hdl->mediaType );

   switch(event)
   {
      case MEDIA_END:
      case MEDIA_ERROR:
      case MEDIA_STOP_TIME_UP:
         kal_trace( TRACE_ERROR, L1AUDIO_DSP_ERROR );
         if( hdl->state == AMR_STATE_PLAY )
            amrCloseDevice( ihdl );
         hdl->state = AMR_STATE_IDLE;
         return event;

      case MEDIA_DATA_REQUEST:
         return MEDIA_DATA_REQUEST;

      default:
         ASSERT(0); // should never reach here
   }
   return MEDIA_NONE;
}

static Media_Event amrMFProcess( MHdl *hdl, Media_Event event )
{
   amrMediaHdl *ihdl = (amrMediaHdl *) hdl;
   ASSERT(ihdl != NULL);

   kal_trace( TRACE_GROUP_AUD_PROCESS, L1AUDIO_PROCESS, hdl->mediaType );

   switch(event)
   {
      case MEDIA_END:
      case MEDIA_ERROR:
      case MEDIA_STOP_TIME_UP:
         kal_trace( TRACE_ERROR, L1AUDIO_DSP_ERROR );
         if( hdl->state == AMR_STATE_PLAY )
            amrCloseDevice( ihdl );
         hdl->uSeekProgress = 0;
         hdl->uCurSeekFrm = 0;
         hdl->state = AMR_STATE_IDLE;
         return event;

      case MEDIA_DATA_REQUEST:
         // Fill media buffer
         if ( false == amrFileMediaBuffer(hdl) )
            return MEDIA_READ_ERROR;
         hdl->waiting = KAL_FALSE;

         {
            kal_uint64 uRatio = 0;
            
            FSAL_GetFileSize(ihdl->pstFSAL, &ihdl->uFileSize);
            if (ihdl->uFileSize) {
               uRatio = (kal_uint64)ihdl->lastFrameOffset * 100 / (kal_uint64)ihdl->uFileSize;
            }
            if( uRatio > ihdl->uDurUpdateThd ){
               if(ihdl->uDurUpdateThd != 100)
                  ihdl->uDurUpdateThd += 20;
               return MEDIA_UPDATE_DUR;
            }
         }

         return MEDIA_NONE;

      default:
         ASSERT(0); // should never reach here
   }
   return MEDIA_NONE;
}

static Media_Status amrMFPlayStrm( MHdl *hdl )
{
   kal_uint16 wCodecMode;
   amrMediaHdl *ihdl = (amrMediaHdl *)hdl;
   kal_bool isHeader = true;

   ihdl->pstFSAL = (STFSAL*)get_ctrl_buffer( sizeof(STFSAL) );
   if(ihdl->pstFSAL == NULL)
      return MEDIA_FAIL;

   ihdl->fileOffset = 0;
   ihdl->end_status = MEDIA_NONE;
   hdl->eof = KAL_FALSE;
   hdl->waiting = KAL_FALSE;
   hdl->current_time = hdl->start_time;
   
   {
      FSAL_Status eFSALStatus;
      FSAL_Direct_SetRamFileSize(ihdl->pstFSAL, hdl->rbInfo.rb_size);
      eFSALStatus = FSAL_Open(ihdl->pstFSAL, hdl->rbInfo.rb_base, FSAL_ROMFILE);
      ASSERT(eFSALStatus == FSAL_OK);
   }

   if(amrChkFileHeader( ihdl ) == KAL_FALSE){
      ihdl->fileOffset = 0;
      isHeader = false;
   }

#if defined(AMRWB_DECODE)
   if (MEDIA_FORMAT_AMR_WB == hdl->mediaType ) { /* AMR-WB */

#if !AMR_AWB_AUDIO_PATH_SUPPORT
#if defined(MT6229)
      DSP_DynamicDownload( DDID_AWB );
      kal_trace( TRACE_STATE, L1AUDIO_INIT_STATE );
#endif
#endif

      ihdl->amr.bWideBand = KAL_TRUE;
      ihdl->amr.vm |= (kal_uint32)MEDIA_VMP_AMR_WB;
      if (isHeader)
         hdl->rbInfo.read += 9;
   }else 
#endif
   if (MEDIA_FORMAT_AMR == hdl->mediaType ) { /* AMR */
      ihdl->amr.bWideBand = KAL_FALSE;
      if (isHeader)
         hdl->rbInfo.read += 6;
   }else {
      ASSERT(KAL_FALSE);
   }

   wCodecMode = amr_SetAndGetInitialCodecMode( ihdl );

   /* write 'No-Data' speech frame to dsp */
   amr_write_speech_mode_to_dsp( ihdl, 0 );
   amrOpenDevice(ihdl , wCodecMode);

   free_ctrl_buffer( ihdl->pstFSAL );
   ihdl->pstFSAL = NULL;

   kal_trace( TRACE_FUNC, L1AUDIO_PLAY_STREAM, hdl->mediaType );

   return MEDIA_SUCCESS;
}

static Media_Status amrMFResumeStrm( MHdl *hdl )
{
   kal_uint16 wCodecMode;
   amrMediaHdl *ihdl = (amrMediaHdl *)hdl;

   ASSERT( hdl->state == AMR_STATE_PAUSE );
   hdl->waiting = KAL_FALSE;

   wCodecMode = amr_SetAndGetInitialCodecMode( ihdl );
         /* write 'No-Data' speech frame to dsp */
   amr_write_speech_mode_to_dsp( ihdl, 0 );

   amrOpenDevice( ihdl , wCodecMode );
   kal_trace( TRACE_FUNC, L1AUDIO_RESUME, hdl->mediaType );
   return MEDIA_SUCCESS;
}


MHdl *AMR_Open(void(*handler)( MHdl *handle, Media_Event event ), STFSAL *pstFSAL, void *param)
{
   MHdl *hdl;
   amrMediaHdl *ihdl;
   Media_VM_PCM_Param *amr_param = (Media_VM_PCM_Param *) param;

   ihdl = (amrMediaHdl*)get_ctrl_buffer( sizeof(amrMediaHdl) );
   memset(ihdl, 0, sizeof(amrMediaHdl));
   hdl = (MHdl *) ihdl;
   if( handler != NULL )
      mhdlInit( hdl, DP_D2C_SD_DONE, amrHisr );
   else
      mhdlInitEx( hdl, DP_D2C_SD_DONE, amrHisr );

   hdl->handler  = handler;
   hdl->state    = AMR_STATE_IDLE;
   ihdl->pstFSAL = pstFSAL;

   ihdl->amr.vm  = amr_param->vmParam;
   hdl->mediaType = amr_param->mediaType;
   
   hdl->Close    = amrMFClose;
   hdl->Pause    = amrMFPause;
   hdl->Stop     = amrMFStop;

   hdl->BuildCache    = amrMFBuildCache;
   hdl->SetCacheTbl  = amrMFSetCacheTbl;
   hdl->GetCacheDuration = amrMFGetCacheDur;
   hdl->GetTotalDuration = amrMFGetTotalDuration;


   if(pstFSAL != NULL){
      ihdl->isStreaming = false;
      hdl->Play = amrMFPlayFile;
      hdl->Resume = amrMFResumeFile;
      hdl->Process  = amrMFProcess;
      kal_trace( TRACE_FUNC, L1AUDIO_OPEN_FILE, MEDIA_FORMAT_AMR );
   }else{
      ihdl->isStreaming = true;
      hdl->Play = amrMFPlayStrm;
      hdl->Resume = amrMFResumeStrm;
      hdl->Process  = amrMFStrmProcess;
      kal_trace( TRACE_FUNC, L1AUDIO_OPEN_STREAM, MEDIA_FORMAT_AMR );
   }

   FSAL_GetFileSize(ihdl->pstFSAL, &ihdl->uFileSize);
   kal_trace( TRACE_GROUP_CACHE, L1AUDIO_CACHE_FILE_SIZE, MEDIA_FORMAT_AMR, ihdl->uFileSize);
   
#if VERIFY_AVSYNC
   ut_a2v_play_dsp_start();
#endif

   return hdl;
}
#if 0
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
#if defined(AMRWB_DECODE)
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
#endif
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
/* under construction !*/
#endif // #if 0

#define AMR_PARSE_FRAME_THRESHOLD 100

static kal_uint32 amrGetByteRate( amrMediaHdl *ihdl )
{
   kal_uint32 i, accumByte;
   kal_uint8 AMR_Header;
   kal_int32 num_bytes, frameLen;
   kal_uint8  FT;
   
   accumByte = 0;
   i = 0;

#if defined(AMRWB_DECODE)
   if (MEDIA_FORMAT_AMR_WB == ihdl->mh.mediaType ) {
   	  /* AMR-WB */
      for (i=0; i<AMR_PARSE_FRAME_THRESHOLD; i++) {
         num_bytes = amrGetBytesFromFile((MHdl*)ihdl, ihdl->pstFSAL, ihdl->fileOffset, 1, &AMR_Header);
         if (num_bytes != 1) {
            break;
         } else {
            FT = (AMR_Header >> 3) & 0x0F;
            
            if (FT <= 9) {
               frameLen = ((lenSpeechBitsAMR_WB[FT]+7)>>3) + 1;
            } else if (FT == 15) {
               frameLen = 1;
            } else if (FT == 14) {
               kal_uint8 FQI = (AMR_Header >> 2) & 0x01;
               
               if (FQI == 0)
                  frameLen = 1;
               else
                  break;
            } else {
               break;
            }
            accumByte += frameLen;
            ihdl->fileOffset += frameLen;
         }
      }
   } else
#endif
   if (MEDIA_FORMAT_AMR == ihdl->mh.mediaType ) {
   	  /* AMR */
      for (i=0; i<AMR_PARSE_FRAME_THRESHOLD; i++) {
         num_bytes = amrGetBytesFromFile((MHdl*)ihdl, ihdl->pstFSAL, ihdl->fileOffset, 1, &AMR_Header);
         if (num_bytes != 1) {
            break;
         } else {
            FT = (AMR_Header >> 3) & 0x0F;
            
            if (FT <= 8) {
               frameLen = ((lenSpeechBitsAMR[FT]+7)>>3) + 1;
            } else if (FT == 15) {
               frameLen = 1;
            } else {
               break;
            }
            accumByte += frameLen;
            ihdl->fileOffset += frameLen;
         }
      }
   }

   if (i)
      return (kal_uint64) accumByte * 50 / i;
   else
      return 0;
}

Media_Status AMR_GetContentDescInfo( STFSAL *pstFSAL, audInfoStruct *contentInfo, void *param  )
{
   amrMediaHdl *ihdl;
   kal_uint32 file_size;
   kal_uint32 byteRate;

   FSAL_GetFileSize( pstFSAL, &file_size);

   ihdl = (amrMediaHdl*)get_ctrl_buffer( sizeof(amrMediaHdl) );
   memset(ihdl, 0, sizeof(amrMediaHdl));
   mhdlInit( ( MHdl *)ihdl, 0xFFFF , amrHisr );
   ihdl->pstFSAL = pstFSAL;

   memset(contentInfo, 0, sizeof(audInfoStruct));

   if(amrChkFileHeader( ihdl ) == KAL_FALSE){
      free_ctrl_buffer(ihdl);
      return MEDIA_BAD_FORMAT;
   }

   byteRate = amrGetByteRate( ihdl );
#if defined(AMRWB_DECODE)
   if (MEDIA_FORMAT_AMR_WB == ihdl->mh.mediaType ) { /* AMR-WB */
      contentInfo->sampleRate = 16000;
      file_size -= 9;
   } else
#endif
   if (MEDIA_FORMAT_AMR == ihdl->mh.mediaType ) { /* AMR */
      contentInfo->sampleRate = 8000;
      file_size -= 6;
   }
   contentInfo->bitRate = byteRate*8;
   if (byteRate)
      contentInfo->time = (kal_uint64) file_size * 1000 / (kal_uint64) byteRate;   
   
   free_ctrl_buffer( ihdl );
   kal_trace( TRACE_FUNC, L1AUDIO_CONTENT, ihdl->mh.mediaType );
   return MEDIA_SUCCESS;
}

Media_Status AMR_GetMetaData( kal_wchar *filename, kal_uint8 *pbBuf, kal_uint32 uSize, audInfoStruct *contentInfo, void *param )
{
   return autGetMetaData( filename, pbBuf, uSize, contentInfo, param, AMR_GetContentDescInfo );
}

Media_Status AMR_Trim(STFSAL *pstInput, STFSAL *pstOutput, kal_uint32 uStartTime, kal_uint32 uStopTime)
{
   amrMediaHdl *ihdl;
   kal_uint32 uStartOffset, uEndOffset;
   Media_Status eResult = MEDIA_SUCCESS;

   if(((kal_int32)uStopTime - (kal_int32)uStartTime) <= 0)
      return MEDIA_BAD_PARAMETER; // invalid stop time

   ihdl = (amrMediaHdl*)get_ctrl_buffer( sizeof(amrMediaHdl) );
   memset(ihdl, 0, sizeof(amrMediaHdl));

   if(amrChkFileHeader( ihdl ) == KAL_FALSE){
      free_ctrl_buffer(ihdl);
      return MEDIA_BAD_FORMAT;
   }

   // find the start/end offset of extraction
   {
	  kal_uint32 uFirstFrameFileOffset = ihdl->fileOffset;
	  ihdl->mh.start_time = uStartTime;
      if (amrSeek(ihdl, false)==KAL_FALSE){
         free_ctrl_buffer(ihdl);
         return MEDIA_SEEK_FAIL;
	  }
	  uStartOffset = ihdl->fileOffset;
	  ihdl->fileOffset = uFirstFrameFileOffset;

      ihdl->mh.start_time = uStopTime;
      if (amrSeek(ihdl, false)==KAL_FALSE){
         free_ctrl_buffer(ihdl);
         return MEDIA_SEEK_FAIL;
	  }
      uEndOffset = ihdl->fileOffset;
	  ihdl->fileOffset = uStartOffset;
	}

   //write fregment audio data
   {
	   FSAL_Status eFSALStatus;
	   kal_int32 uNumBytes;
	   kal_int32 uLength = (kal_int32)uEndOffset-(kal_int32)uStartOffset;

	   // refill buffer
	   while(uLength > 0){
		  kal_uint32 len = MAX_DATA_REQUESTED;
		  if(uLength<MAX_DATA_REQUESTED)
			  len = uLength;
		  uNumBytes = amrGetBytesFromFile((MHdl *)ihdl, ihdl->pstFSAL, ihdl->fileOffset, len, &ihdl->buf[0]);
		  if(uNumBytes<0){
             free_ctrl_buffer(ihdl);
             return MEDIA_READ_FAIL;
		  }
	      eFSALStatus = FSAL_Write(pstOutput, &ihdl->buf[0], uNumBytes);
		  if(FSAL_OK != eFSALStatus){
             free_ctrl_buffer(ihdl);
             if(eFSALStatus == FS_DISK_FULL)
                return MEDIA_DISK_FULL;
             else
                return MEDIA_WRITE_FAIL;
		  }
		  ihdl->fileOffset += uNumBytes;
		  uLength -= uNumBytes;

	   }
   }
   free_ctrl_buffer(ihdl);
   return eResult;
}

#if MBM_IMIBASE_SUPPORT
#define IME_SOUNDBUF_SIZE (256)
typedef void* (*malloc_func)(int size);
typedef void (*free_func)(void *p);
typedef void (*callback_func)(void *param, int evt);

static malloc_func s_amr_malloc;
static free_func s_amr_free;
static callback_func s_amr_callback;

void miiplt_audio_init(void *m, void *f, void *cb)
{
    s_amr_malloc = (malloc_func)m;
    s_amr_free = (free_func)f;
    s_amr_callback = (callback_func)cb;    
}

static void ime_amr_handler(MHdl *handle, Media_Event evt)
{
    int data = 1;

    if (MEDIA_ERROR == evt)
    {
        data = 0;
    }

    if (s_amr_callback)
    {
        (*s_amr_callback)((void*)handle, (int)data);
    }
}

static void ime_amrHisr( void *data )
{
   kal_int32  uBytesAvailable;
   kal_uint16 rd_bytes, lenSherifData, rd_bits;
   kal_uint8  bFrameHeader, FT, Tx_Type;

   amrMediaHdl *ihdl = (amrMediaHdl *)data;

   if (ihdl->mh.state != AMR_STATE_PLAY || ihdl->end_status == MEDIA_END
       || ihdl->end_status == MEDIA_ERROR || ihdl->end_status == MEDIA_TERMINATED)
      return;

   ihdl->mh.handler( (MHdl*)ihdl, ihdl->end_status );

   if(ihdl->mh.waiting == true){
      amr_write_speech_mode_to_dsp( ihdl, 0 );
	  return;
   }

   /* check ring buffer emptiness */
   uBytesAvailable = ihdl->mh.GetDataCount((MHdl *)ihdl);
   uBytesAvailable --;  /* 1 byte for header */

   if (uBytesAvailable < 1) {
      /* media buffer underflows */
      /* stuff 'no-data' frame */
	 // kal_prompt_trace(MOD_USB, "amrHisr dumb dsp: uBytesAvailable < 1\n");
	  ihdl->mh.waiting = true;
      amr_write_speech_mode_to_dsp( ihdl, 0 );
      return;
   }

   /* decode frame header */
   bFrameHeader = media_buffer_peek_byte( ihdl );
   FT = (bFrameHeader >> 3) & 0x0F;

#if defined(AMRWB_DECODE)
   // kal_prompt_trace(MOD_USB, "amrHisr AMRWB_DECODE");
   if (KAL_TRUE==ihdl->amr.bWideBand) {
      /* If the codec mode are not right, DSP may mal-function */
      EXT_ASSERT((((((*DP_SD2_AWB_VM_0)>>8)&0xFF)>=0x20) &&
        ((((*DP_SD2_AWB_VM_0)>>8)&0xFF)<=0x28)), (*DP_SD2_AWB_VM_0), 0, 0);

      {
         kal_int32 ret;
         ret = amrWBChkFrameHeader( ihdl, bFrameHeader, &Tx_Type, &lenSherifData, &rd_bits );
         if(ret == HEADER_ERROR){
		    kal_prompt_trace(MOD_USB, "amrHisr HEADER_ERROR\n");
            //media_playback_error(ihdl);
            return;
         }else if(ret == HEADER_NO_DATA_FRAME){
			 kal_prompt_trace(MOD_USB, "amrHisr HEADER_NO_DATA_FRAME\n");
             media_buffer_get_byte(ihdl);   /* consume the header byte */
             amr_write_speech_mode_to_dsp(ihdl, Tx_Type);
             return;
          }
      }

   } else
#endif
   {
    //kal_prompt_trace(MOD_USB, "amrHisr amrChkFrameHeader");
      /* If the codec mode are not right, DSP may mal-function */
      EXT_ASSERT(((*DP_SD2_VM_17>=0x03) && (*DP_SD2_VM_17<=0x0A)), (*DP_SD2_VM_17), 0, 0);
      {
         kal_uint32 ret;
         ret = amrChkFrameHeader( ihdl, bFrameHeader, &Tx_Type, &lenSherifData, &rd_bits );
         if(ret == HEADER_ERROR){
			 kal_prompt_trace(MOD_USB, "amrHisr HEADER_ERROR\n");
             media_playback_error(ihdl);
             return;
         }else if(ret == HEADER_NO_DATA_FRAME){
			 kal_prompt_trace(MOD_USB, "amrHisr HEADER_NO_DATA_FRAME\n");
             media_buffer_get_byte(ihdl);   /* consume the header byte */
             amr_write_speech_mode_to_dsp(ihdl, Tx_Type);
             return;
          }
      }
   }

   /* check buffer underflow */
   rd_bytes = (rd_bits+7)>>3;
   if (rd_bytes > uBytesAvailable) {
	  //kal_prompt_trace(MOD_USB, "amrHisr dumb dsp: rd_byte=%d, available=%d\n", rd_bytes, uBytesAvailable);
	  ihdl->mh.waiting = true;
      amr_write_speech_mode_to_dsp( ihdl, 0 );
      return;
   }

   media_buffer_get_byte(ihdl);   /* consume the header byte */
   /* get speech data from ring buffer */
   media_buffer_get_nbytes( ihdl, (kal_uint8*)ihdl->Tx_ordering_buffer, rd_bytes);

   /* bits re-ordering */
   kal_mem_set(ihdl->Tx_data_buffer, 0, sizeof(ihdl->Tx_data_buffer));
   amr_bit_ordering_file_to_sherif(ihdl, Tx_Type, FT, ihdl->Tx_ordering_buffer, ihdl->Tx_data_buffer, rd_bits);

   if (Tx_Type==3) { /* SID first or SID update */
      ihdl->Tx_data_buffer[2]&=0x00E0;
      if((ihdl->Tx_ordering_buffer[2]&0x0010)==0)   /* STI */
         Tx_Type = 1; /* SID first */
   }

   /* write speech frame to sherif */
   //kal_prompt_trace(MOD_USB, "amrHisr amr_write_speech_data_to_dsp: %d-%d", Tx_Type, lenSherifData);
   amr_write_speech_mode_to_dsp(ihdl, Tx_Type);
   amr_write_speech_data_to_dsp(ihdl, ihdl->Tx_data_buffer, lenSherifData);
   
}

void *ime_AMR_Sound_Open(void)
{
   MHdl *hdl;
   amrMediaHdl *ihdl;
   uint16 wCodecMode;


   ihdl = (amrMediaHdl*)get_ctrl_buffer( sizeof(amrMediaHdl) );
   memset(ihdl, 0, sizeof(amrMediaHdl));
   hdl = (MHdl*)ihdl;

   mhdlInit( hdl, DP_D2C_SD_DONE, ime_amrHisr);

   hdl->handler  = ime_amr_handler;
   hdl->state    = AMR_STATE_IDLE;
   //hdl->read += 6;
   //hdl->SetBuffer(hdl, (kal_uint8*) aud_context_p->ring_buf, AUD_RING_BUFFER_LEN * 2);
   hdl->rbInfo.rb_size = IME_SOUNDBUF_SIZE*2;
  hdl->rbInfo.rb_base = (*s_amr_malloc)(hdl->rbInfo.rb_size);
   hdl->rbInfo.write = 0;
   hdl->rbInfo.read = 0;
   hdl->eof = false;
   hdl->waiting = true;

   ihdl->end_status = MEDIA_NONE;
   ihdl->amr.bWideBand = KAL_FALSE;
#if 0
   ihdl->amr.vm  = (uint32)MEDIA_VMP_AS_SPEECH;
#else
   // kal_prompt_trace(MOD_USB, "MEDIA_VMP_AS_RINGTONE");
   ihdl->amr.vm  = (uint32) MEDIA_VMP_AS_RINGTONE;
#endif

   wCodecMode = amr_SetAndGetInitialCodecMode( ihdl );

   /* write 'No-Data' speech frame to dsp */
   amr_write_speech_mode_to_dsp( ihdl, 0 );
   amrOpenDevice(ihdl , wCodecMode);

   kal_prompt_trace(MOD_USB, "ime_AMR_Sound_Open");

   return (void*)hdl;
}

kal_uint32 miiplt_audio_amr_write(void *hdl1, void *buf, kal_uint32 count)
{
	MHdl *hdl = hdl1;
    kal_uint8 *pBuf;
    kal_uint32 num_bytes;
    kal_uint32 buf_len;
    
	//kal_prompt_trace(MOD_USB, "AMR_Sound_Write: 0x%x", (int32)hdl1);
	//kal_prompt_trace(MOD_USB, "GetWriteBuffer: 0x%x", (int32)hdl->GetWriteBuffer);

    hdl->GetWriteBuffer(hdl, &pBuf, &buf_len);	

	if(buf_len > count)
		buf_len = count;
    kal_mem_cpy(pBuf, buf, buf_len);
    
	//kal_prompt_trace(MOD_USB, "WriteDataDone: 0x%x", hdl->WriteDataDone);
    hdl->WriteDataDone(hdl, buf_len);

    num_bytes = hdl->GetDataCount(hdl);
    
    if(num_bytes>(IME_SOUNDBUF_SIZE))
		hdl->waiting = false;

    //kal_prompt_trace(MOD_USB, "GetWriteBuffer: 0x%x, %d, %d\n",(int32)pBuf, buf_len, num_bytes);
    
	return buf_len;
}

void ime_AMR_Sound_Ctrl(void *handle, int pause)
{
    //kal_prompt_trace(MOD_USB, "ime_AMR_Sound_Ctrl: %d", pause);
    
    if (pause)
    {
		if(((MHdl *)handle)->state == AMR_STATE_PLAY)
			amrMFPause((MHdl *)handle);
    }
    else
    {
		if(((MHdl *)handle)->state == AMR_STATE_PAUSE)
			amrMFResumeFile((MHdl *)handle);
    }
}

void ime_AMR_Sound_Close(void *hdl)
{
    amrMediaHdl *ihdl = (amrMediaHdl*)hdl;

    kal_prompt_trace(MOD_USB, "ime_AMR_Sound_Close");

    if (AMR_STATE_PLAY == ihdl->mh.state)
    {
	    amrCloseDevice(ihdl);
	}
	
	mhdlFinalize(hdl, DP_D2C_SD_DONE);
	(*s_amr_free)(ihdl->mh.rbInfo.rb_base);
	free_ctrl_buffer(hdl);
}
#endif //MBM_IMIBASE_SUPPORT
#endif   // #if defined(AMR_CODEC)
